其他
Java各类日志组件分析汇总
本文将和大家介绍一下 Java 主流的日志工具,以及相对应的使用场景。
▐ JUL
import java.util.logging.Level;
import java.util.logging.Logger;
private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName());
相同名字的Logger对象全局只有一个; 一般使用圆点分隔的层次命名空间来命名 Logger;Logger 名称可以是任意的字符串,但是它们一般应该基于被记录组件的包名或类名,如 java.net 或 javax.swing; 配置文件默认使用jre/lib/logging.properties,日志级别默认为INFO; 可以通过系统属性java.util.logging.config.file指定路径覆盖系统默认文件; 日志级别由高到低依次为:SEVERE(严重)、WARNING(警告)、INFO(信息)、CONFIG(配置)、FINE(详细)、FINER(较详细)、FINEST(非常详细)。另外还有两个全局开关:OFF「关闭日志记录」和ALL「启用所有消息日志记录」。 《logging.properties》文件中,默认日志级别可以通过.level= ALL来控制,也可以基于层次命名空间来控制,按照Logger名字进行前缀匹配,匹配度最高的优先采用;日志级别只认大写; JUL通过handler来完成实际的日志输出,可以通过配置文件指定一个或者多个hanlder,多个handler之间使用逗号分隔;handler上也有一个日志级别,作为该handler可以接收的日志最低级别,低于该级别的日志,将不进行实际的输出;handler上可以绑定日志格式化器,比如java.util.logging.ConsoleHandler就是使用的String.format来支持的;
handlers= java.util.logging.ConsoleHandler
.level= ALL
com.suian.logger.jul.xxx.level = CONFIG
com.suian.logger.jul.xxx.demo2.level = FINE
com.suian.logger.jul.xxx.demo3.level = FINER
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tF %1$tT [%4$s] %3$s - %5$s %n
▐ Apache Commons Logging
org.apache.commons.logging.impl.Jdk14Logger,适配JDK1.4里的JUL; org.apache.commons.logging.impl.Log4JLogger,适配Log4J; org.apache.commons.logging.impl.LogKitLogger,适配avalon-Logkit; org.apache.commons.logging.impl.SimpleLog,common-logging自带日志实现类,它实现了Log接口,把日志消息都输出到系统错误流System.err中; org.apache.commons.logging.impl.NoOpLog,common-logging自带日志实现类,它实现了Log接口,其输出日志的方法中不进行任何操作;
▐ Avalon LogKit
Avalon LogKit是一个高速日志记录工具集,Avalon里的各个组件Framework、Excalibur、Cornerstone和Phoenix都用到它。它的模型与JDK 1.4 Logging package采用相同的原理,但与JDK 1.2+兼容。使用LogKit的原因是:Context和LogTargets。
使用Log4j的时候,日志的内容只能是一句话,而使用LogKit,你可以记录很多项内容,甚至可以各项内容记录到对应的数据库字段中。如果使用Log4j存储日志到不同的存储介质,如数据库,需要使用Appender,而LogKit已经可以支持多种存储目标。
▐ log4j
Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、数据库等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
Log4j有7种不同的log级别,按照等级从低到高依次为:TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF。如果配置为OFF级别,表示关闭log。Log4j支持两种格式的配置文件:properties和xml。包含三个主要的组件:Logger、appender、Layout。
▐ SLF4J
SLF4J全称The Simple Logging Facade for Java,简单日志门面,这个不是具体的日志解决方案,而是通过门面模式提供一些Java Logging API,类似于JCL。题外话,作者当时创建SLF4J的目的就是为了替代Jakarta Commons Logging(JCL)。
SLF4J提供的核心API是一些接口以及一个LoggerFactory的工厂类。在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定真正的Log库。
使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
logback是slf4j-api的天然实现,不需要桥接包就可以使用。另外slf4j还封装了很多其他的桥接包,可以使用到其他的日志实现中,比如slf4j-log4j12,就可以使用log4j进行底层日志输出,再比如slf4j-jdk14,可以使用JUL进行日志输出。
▐ Logback
Logback,一个“可靠、通用、快速而又灵活的Java日志框架”。Logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本,完整实现了SLF4J API。
logback-access模块与Servlet容器集成提供通过Http来访问日志的功能。Logback依赖配置文件logback.xml,当然也支持groovy方式。Logback相比log4j,有很多很多的优点,网上一搜一大片,此处就不再赘述了。
▐ Log4j2
Log4j 2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。
Log4j2支持插件式结构,可以根据需要自行扩展Log4j2,实现自己的appender、logger、filter等。在配置文件中可以引用属性,还可以直接替代或传递到组件,而且支持json格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。
Log4j2利用Java5中的并发特性支持,尽可能地执行最低层次的加锁。解决了在log4j 1.x中存留的死锁的问题。Log4j 2是基于LMAX Disruptor库的。在多线程的场景下,和已有的日志框架相比,异步logger拥有10倍左右的效率提升。
Log4j2体系结构:
使用场景
▐ 只使用java.util.logging.Logger
▐ 只使用Apache Commons Logging
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
▐ Apache Commons Logging和log4j结合使用
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
org.apache.commons.logging.Log,commons-logging里的api; org.apache.log4j.Logger,log4j里的api;
#log4j.rootLogger = error,console
log4j.logger.com.suian.logtest = trace,console
#输出源console输出到控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c - [log4j]%m%n
既然是推荐使用commons-logging里的api打点,为了能找到log4j的日志实现,必须通过《commons-logging.properties》配置文件显式的确定关联,示例如下:
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
代码中使用JCL api进行日志打点,底层使用log4j进行日志输出。日志输出控制依托于log4j的配置文件,另外需要在commons-logging.properties配置文件中显式指定与log4j的绑定关系。
▐ 单独使用log4j
这个是早几年最最流行的用法了,现在因为log4j本身的问题以及新的日志框架的涌现,已经逐步退出历史舞台了。具体怎么用自己去百度吧。
▐ SLF4J结合Logback
当下最流行的用法,SLF4J为使用场景最广泛的日志门面,加上Logback的天然实现,简单、统一、快速。
需要引入第三方依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
▐ 单独使用Log4j2
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
冲突处理
java.util.logging.Logger,jdk自带的; org.apache.commons.logging.Log,commons-logging包里的api; org.apache.log4j.Logger,log4j包里的api; org.apache.logging.log4j.Logger,log4j2提供的api,在log4j-api包里; org.slf4j.Logger,slf4j提供的api,在slf4j-api包里;
所谓的绑定器,也可以称之为适配器或者包装类,就是将特定api打点的日志绑定到具体日志实现组件来输出。比如JCL可以绑定到log4j输出,也可以绑定到JUL输出;再比如slf4j,可以通过logback输出,也可以绑定到log4j、log4j2、JUL等; 所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。
slf4j整合日志输出
▐ java.util.logging.Logger
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
只引入依赖并不能整合JUL日志,该包里只是提供了一个JUL的handler,仍旧需要通过JUL的配置文件进行配置,slf4j绑定器(如logback)上设置的日志级别等价于JUL handler上的日志级别,因此控制JUL的日志输出,日志级别仍旧分两个地方控制:JUL配置文件《logging.properties》和slf4j绑定器的配置文件,比如《logback.xml》、《log4j2.xml》等。
建立jdk14-logger的配置文件《logger.properties》,加入handler配置以及日志级别配置;
handlers= org.slf4j.bridge.SLF4JBridgeHandler
.level= ALL
在启动程序或容器的时候加入JVM参数配置-Djava.util.logging.config.file = /path/logger.properties;当然也可以使用编程方式进行处理,可以在main方法或者扩展容器的listener来作为系统初始化完成;此种方式有些场景下不如配置JVM参数来的彻底,比如想代理tomcat的系统输出日志,编程方式就搞不定了。
▐ org.apache.commons.logging.Log
将JCL日志整合到slf4j统一输出,需要引入slf4j提供的依赖包:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
jcl-over-slf4j包里所有类的根路径为org.apache.commons.logging,也有Log和LogFactory类,相当于以重写commons-logging包的代价来实现对JCL的桥接。Log与commons-logging包里的一模一样,LogFactory的实现,代码写死使用的是org.apache.commons.logging.impl.SLF4JLogFactory。
commons-logging包里默认使用的是org.apache.commons.logging.impl.LogFactoryImpl。以这样的代价来实现桥接,可以实现无缝对接,不像JUL那样还得添加额外配置,但是有一个坏处就是需要处理类库冲突了。commons-logging包和jcl-over-slf4j包肯定是不能共存的,需要将commons-logging包在classpath里排掉。
题外话,因为JCL本身就支持通过配置文件《commons-logging.properties》绑定适配器,所以个人感觉更倾向于封装一个适配器的方式来支持,就像commons-logging包里的org.apache.commons.logging.impl.Log4JLogger,这样更符合程序员的思维,明明白白。
桥接包的命名也是很讲究的,覆写的这种,命名为xxx-over-slf4j,如本例的jcl-over-slf4j;纯桥接的,命名为xxx-to-slf4j,如文章前面提到的jul-to-slf4j。
▐ org.apache.log4j.Logger
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
看桥接包的名字就知道了,log4j-over-slf4j肯定是覆写了log4j:log4j包,因此使用起来只需要引入依赖即可,不需要其他额外的配置。但是仍旧是要处理冲突的,log4j包和log4j-over-slf4j是不能共存的哦。
▐ org.apache.logging.log4j.Logger
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.6.2</version>
</dependency>
▐ java.util.logging.Logger
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jul</artifactId>
<version>2.6.2</version>
</dependency>
▐ org.apache.commons.logging.Log
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.6.2</version>
</dependency>
首先根据系统属性org.apache.commons.logging.LogFactory查找LogFactory实现类; 如果找不到,则以SPI方式查找实现类,META-INF/services/org.apache.commons.logging.LogFactory;log4j-jcl就是以这种方式支撑的;此种方式必须确保整个应用中,包括应用依赖的第三方jar包中,org.apache.commons.logging.LogFactory文件只有一个,如果存在多个的话,哪个先被加载则以哪个为准。万一存在冲突的话,排查起来也挺麻烦的。 还找不到,则读取《commons-logging.properties》配置文件,使用org.apache.commons.logging.LogFactory属性指定的LogFactory实现类; 最后再找不到,就使用默认的实现org.apache.commons.logging.impl.LogFactoryImpl。
▐ org.apache.log4j.Logger
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.6.2</version>
</dependency>
▐ org.slf4j.Logger
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.6.2</version>
</dependency>
slf4j → logback slf4j → slf4j-log4j12 → log4j slf4j → log4j-slf4j-impl → log4j2 slf4j → slf4j-jdk14 → jul slf4j → slf4j-jcl → jcl jcl → jul jcl → log4j log4j2-api → log4j2-cor log4j2-api → log4j-to-slf4j → slf4j
就是现在,阿里巴巴淘系技术部行业与智能运营团队,Java工程师、技术专家、架构师面向社会+校园招聘,base杭州阿里巴巴西溪园区!
投喂简历给我们:mingkai.wmk@alibaba-inc.com